package com.segment.analytics;
import static junit.framework.Assert.fail;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.annotation.Config.NONE;
import android.net.Uri;
import com.segment.analytics.internal.Private;
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = NONE)
public class ClientTest {
@Rule public MockWebServerRule server = new MockWebServerRule();
@Rule public TemporaryFolder folder = new TemporaryFolder();
private Client client;
private Client mockClient;
@Private HttpURLConnection mockConnection;
@Before
public void setUp() {
mockConnection = mock(HttpURLConnection.class);
client =
new Client(
"foo",
new ConnectionFactory() {
@Override
protected HttpURLConnection openConnection(String url) throws IOException {
String path = Uri.parse(url).getPath();
URL mockServerURL = server.getUrl(path);
return super.openConnection(mockServerURL.toString());
}
});
mockClient =
new Client(
"foo",
new ConnectionFactory() {
@Override
protected HttpURLConnection openConnection(String url) throws IOException {
return mockConnection;
}
});
}
@Test
public void upload() throws Exception {
server.enqueue(new MockResponse());
Client.Connection connection = client.upload();
assertThat(connection.os).isNotNull();
assertThat(connection.is).isNull();
assertThat(connection.connection.getResponseCode()).isEqualTo(200); // consume the response.
RecordedRequestAssert.assertThat(server.takeRequest())
.hasRequestLine("POST /v1/import HTTP/1.1")
.containsHeader("Content-Type", "application/json")
.containsHeader("Content-Encoding", "gzip")
.containsHeader("Authorization", "Basic Zm9vOg==");
}
@Test
public void attribution() throws Exception {
server.enqueue(new MockResponse());
Client.Connection connection = client.attribution();
assertThat(connection.os).isNotNull();
assertThat(connection.is).isNull();
assertThat(connection.connection.getResponseCode()).isEqualTo(200); // consume the response.
RecordedRequestAssert.assertThat(server.takeRequest())
.hasRequestLine("POST /v1/attribution HTTP/1.1")
.containsHeader("Content-Type", "application/json")
.containsHeader("Authorization", "Basic Zm9vOg==");
}
@Test
public void closingUploadConnectionClosesStreams() throws Exception {
OutputStream os = mock(OutputStream.class);
when(mockConnection.getOutputStream()).thenReturn(os);
when(mockConnection.getResponseCode()).thenReturn(200);
Client.Connection connection = mockClient.upload();
verify(mockConnection).setDoOutput(true);
verify(mockConnection).setChunkedStreamingMode(0);
connection.close();
verify(mockConnection).disconnect();
verify(os).close();
}
@Test
public void closingUploadConnectionClosesStreamsForNon200Response() throws Exception {
OutputStream os = mock(OutputStream.class);
when(mockConnection.getOutputStream()).thenReturn(os);
when(mockConnection.getResponseCode()).thenReturn(202);
Client.Connection connection = mockClient.upload();
verify(mockConnection).setDoOutput(true);
verify(mockConnection).setChunkedStreamingMode(0);
connection.close();
verify(mockConnection).disconnect();
verify(os).close();
}
@Test
public void uploadFailureClosesStreamsAndThrowsException() throws Exception {
OutputStream os = mock(OutputStream.class);
InputStream is = mock(InputStream.class);
when(mockConnection.getOutputStream()).thenReturn(os);
when(mockConnection.getResponseCode()).thenReturn(300);
when(mockConnection.getResponseMessage()).thenReturn("bar");
when(mockConnection.getInputStream()).thenReturn(is);
Client.Connection connection = mockClient.upload();
verify(mockConnection).setDoOutput(true);
verify(mockConnection).setChunkedStreamingMode(0);
try {
connection.close();
fail(">= 300 return code should throw an exception");
} catch (Client.HTTPException e) {
assertThat(e)
.hasMessage(
"HTTP 300: bar. "
+ "Response: Could not read response body for rejected message: "
+ "java.io.IOException: Underlying input stream returned zero bytes");
}
verify(mockConnection).disconnect();
verify(os).close();
}
@Test
public void fetchSettings() throws Exception {
server.enqueue(new MockResponse());
Client.Connection connection = client.fetchSettings();
assertThat(connection.os).isNull();
assertThat(connection.is).isNotNull();
assertThat(connection.connection.getResponseCode()).isEqualTo(200);
RecordedRequestAssert.assertThat(server.takeRequest())
.hasRequestLine("GET /v1/projects/foo/settings HTTP/1.1")
.containsHeader("Content-Type", "application/json");
}
@Test
public void fetchSettingsFailureClosesStreamsAndThrowsException() throws Exception {
when(mockConnection.getResponseCode()).thenReturn(204);
when(mockConnection.getResponseMessage()) //
.thenReturn("no cookies for you http://bit.ly/1EMHBNb");
try {
mockClient.fetchSettings();
fail("Non 200 return code should throw an exception");
} catch (IOException e) {
assertThat(e).hasMessage("HTTP " + 204 + ": no cookies for you http://bit.ly/1EMHBNb");
}
verify(mockConnection).disconnect();
}
@Test
public void closingFetchSettingsClosesStreams() throws Exception {
InputStream is = mock(InputStream.class);
when(mockConnection.getInputStream()).thenReturn(is);
when(mockConnection.getResponseCode()).thenReturn(200);
Client.Connection connection = mockClient.fetchSettings();
connection.close();
verify(mockConnection).disconnect();
verify(is).close();
}
static class RecordedRequestAssert
extends AbstractAssert<RecordedRequestAssert, RecordedRequest> {
static RecordedRequestAssert assertThat(RecordedRequest recordedRequest) {
return new RecordedRequestAssert(recordedRequest);
}
protected RecordedRequestAssert(RecordedRequest actual) {
super(actual, RecordedRequestAssert.class);
}
public RecordedRequestAssert containsHeader(String name, String expectedHeader) {
isNotNull();
String actualHeader = actual.getHeader(name);
Assertions.assertThat(actualHeader)
.overridingErrorMessage(
"Expected header <%s> to be <%s> but was <%s>.", name, expectedHeader, actualHeader)
.isEqualTo(expectedHeader);
return this;
}
public RecordedRequestAssert containsHeader(String name) {
isNotNull();
String actualHeader = actual.getHeader(name);
Assertions.assertThat(actualHeader)
.overridingErrorMessage(
"Expected header <%s> to not be empty but was.", name, actualHeader)
.isNotNull()
.isNotEmpty();
return this;
}
public RecordedRequestAssert hasRequestLine(String requestLine) {
isNotNull();
String actualRequestLine = actual.getRequestLine();
Assertions.assertThat(actualRequestLine)
.overridingErrorMessage(
"Expected requestLine <%s> to be <%s> but was not.", actualRequestLine, requestLine)
.isEqualTo(requestLine);
return this;
}
}
}